#include "Ray_Util.h"
#include <math.h>

Pixel::Pixel(){
	red=0;
	green=0;
	blue=0;
}

Pixel::~Pixel()
{

}

void Pixel::SetColor( float r, float g, float b )
{
	red=r;
	green=g;
	blue=b;
}
Screen::Screen(int width, int height, float windowL, float windowR, float windowT, float windowB){
	screenBuffer=new Pixel[width*height]();
	screenWidth=width;
	screenHeight=height;
	windowLeft=windowL;
	windowRight=windowR;
	windowTop=windowT;
	windowBottom=windowB;
	windowHorizontalStep=(windowR-windowL)/(float)width;
	windowVerticalStep=(windowB-windowT)/(float)height;
}

Screen::~Screen(){
	delete [] screenBuffer;
}

Pixel* Screen::GetScreenBuffer(){
	return screenBuffer;
}

void Ray::SetOrigin( float x,float y, float z )
{
	origin.set(x,y,z);
}

void Ray::SetDirection( float x, float y, float z )
{
	direction.set(x,y,z);
}

void Object::SetType( int geo)
{
	geometryType=geo;	
}

int Object::GetGeometryType()
{
	return geometryType;
}

int Object::GetOpticalType()
{
	return opticalType;
}

Material* Object::GetMaterial()
{
	return &material;
}

void Object::SetLight( int opt )
{
	opticalType=opt;
}
void Material::SetReflaction( float refl )
{
	reflaction=refl;
}

void Material::SetOpticalFactor( float diff )
{
	diffuse=diff;
	specular=1.0f-diff;
}

void Material::SetMaterialColor( float r, float g, float b )
{
	color.set(r,g,b);
}

Sphere::Sphere( float r, vector3& c, int opt )
{
	center=c;
	radius=r;
	squareRadius=r*r;
	reverseRadius=1.0f/r;
	SetType(SPHERE);
	SetLight(opt);
}

float Sphere::GetRadius()
{
	return radius;
}

float Sphere::GetSquareRadius()
{
	return reverseRadius;
}

vector3& Sphere::GetCenter()
{
	return center;
}

int Sphere::IntersectTest( Ray& ray, float& dist )
{
	vector3 v = ray.origin - center;
	float b = -DotProduct(v,ray.direction);
	float det = (b * b) - DotProduct(v,v) + squareRadius;
	int retval = MISS;
	if (det > 0)
	{
		det = sqrtf( det );
		float i1 = b - det;
		float i2 = b + det;
		if (i2 > 0)
		{
			if (i1 < 0) 
			{
				if (i2 < dist) 
				{
					dist = i2;
					retval = INPRIM;
				}
			}
			else
			{
				if (i1 < dist)
				{
					dist = i1;
					retval = HIT;
				}
			}
		}
	}
	return retval;
}

vector3 Sphere::GetNormal( vector3& point )
{
	return (point - center) * reverseRadius;
}

Plane::Plane( vector3& n, float dist, int opt )
{
	normal=n.normalize();
	distance=dist;
	SetType(PLANE);
	SetLight(opt);
}

float Plane::GetDistance()
{
	return distance;
}

vector3 Plane::GetNormal( vector3& point )
{
	return normal;
}

int Plane::IntersectTest( Ray& ray, float& dist )
{
	float d = DotProduct(normal,ray.direction);
	if (d != 0)
	{
		float dis =-(DotProduct(normal,ray.origin)+distance)/d;
		if (dis > 0)
		{
			if (dis < dist) 
			{
				dist = dis;
				return HIT;
			}
		}
	}
	return MISS;
}

int Scene::GetObjectsNum()
{
	return numObjects;
}

Object* Scene::GetObjectAt( int index )
{
	return objects[index];
}

void Scene::InitScene()
{
	objects=new Object*[100];

	objects[0]=new Plane(vector3(0,1,0),4.4f,NONLIGHT);
	objects[0]->GetMaterial()->SetMaterialColor(0.4f, 0.3f, 0.3f);//color.set();
	objects[0]->GetMaterial()->SetOpticalFactor(1.0f);//diffuse=1.0f;
	objects[0]->GetMaterial()->SetReflaction(0.0f);//reflaction=0.0f;

	objects[1]=new Sphere(2.5f,vector3(1, -0.8f, 3),NONLIGHT);
	objects[1]->GetMaterial()->SetMaterialColor(0.7f, 0.7f, 0.7f);//color.set(0.7f, 0.7f, 0.7f);
	objects[1]->GetMaterial()->SetReflaction(0.6f);//reflaction=0.6f;
	objects[1]->GetMaterial()->SetOpticalFactor(1.0f);//diffuse=1.0f;

	objects[2]=new Sphere(2.0f,vector3(-5.5f, -0.5, 7),NONLIGHT);
	objects[2]->GetMaterial()->SetMaterialColor(0.7f, 0.7f, 1.0f);//color.set();
	objects[2]->GetMaterial()->SetOpticalFactor(0.1f);//diffuse=0.1f;
	objects[2]->GetMaterial()->SetReflaction(1.0f);//reflaction=1.0f;

	objects[3]=new Sphere(0.1f,vector3(0, 5, 5),LIGHT);
	objects[3]->GetMaterial()->SetMaterialColor(0.6f, 0.6f, 0.6f);
	objects[3]->GetMaterial()->SetOpticalFactor(0.0f);
	objects[3]->GetMaterial()->SetReflaction(0.0f);

	objects[4]=new Sphere(0.1f,vector3(2, 5, 1),LIGHT);
	objects[4]->GetMaterial()->SetMaterialColor(0.7f, 0.7f, 0.9f);
	objects[4]->GetMaterial()->SetOpticalFactor(0.0f);
	objects[4]->GetMaterial()->SetReflaction(0.0f);

	numObjects=5;

}

Engine::Engine( int width, int height, float windowL, float windowR, float windowT, float windowB )
{
	screen=new Screen(width,height,windowL,windowR,windowT,windowB);
	scene=new Scene();
	scene->InitScene();
	currentLine=0;
	currentX=screen->windowLeft;
	currentY=screen->windowTop;
}

Engine::~Engine()
{
	delete screen;
	delete scene;
}

bool Engine::Render()
{
	Pixel* buffer=screen->GetScreenBuffer();
	vector3 eyePosition( 0, 0, -5 );
	clock_t start,end;

	for ( int y = currentLine; y < screen->screenHeight; y++ )
	{
		currentX = screen->windowLeft;
		start=clock();

		for ( int x = 0; x < screen->screenWidth; x++ )
		{

			vector3 pixelColor( 0, 0, 0 );
			vector3 dir = vector3( currentX, currentY, 0 ) - eyePosition;
			dir=dir.normalize();
			Ray ray;
			ray.SetOrigin(eyePosition.x,eyePosition.y,eyePosition.z);
			ray.SetDirection(dir.x,dir.y,dir.z);
			float dist;
			dist=MAXDISTANCE;
			RayTrace(ray,pixelColor,1,dist);
			buffer[y*screen->screenWidth+x].SetColor(pixelColor.x,pixelColor.y,pixelColor.z);
			
			currentX += screen->windowHorizontalStep;
		}
		currentY += screen->windowVerticalStep;

		end=clock();

		if (end-start>10)
		{
			currentLine=y+1;
			return false;
		}
		
	}
	return true;
}

Scene* Engine::GetScene()
{
	return scene;
}

Screen* Engine::GetScreen()
{
	return screen;
}

Object* Engine::RayTrace( Ray& ray, vector3& color, int depth, float dist )
{
	if (depth > MAXDEPTH) return 0;

	Object* object = 0;
	int result=0;

	for ( int x = 0; x < scene->GetObjectsNum(); x++ )
	{
		Object* obj = scene->GetObjectAt(x);
		int res;
		if (res = obj->IntersectTest(ray,dist))
		{
			object = obj;
			result = res; 
		}
	}

	if (!object) return 0;

	if (object->GetOpticalType()==LIGHT)
	{
		color = vector3( 1, 1, 1 );
	}
	else
	{
		
		vector3 point=ray.origin+ray.direction*dist;

		for ( int x = 0; x < scene->GetObjectsNum(); x++ )
		{
			Object* obj = scene->GetObjectAt(x);
			if (obj->GetOpticalType()==LIGHT)
			{
				Object* light = obj;

				vector3 L = ((Sphere*)light)->GetCenter() - point;
				L=L.normalize();
				vector3 N = object->GetNormal(point);
				if (object->GetMaterial()->diffuse>0)
				{
					float dotProduct = DotProduct(N,L);// DOT( N, L );
					if (dotProduct > 0)
					{
						float diff = dotProduct * object->GetMaterial()->diffuse;

						color += diff * VectorProduct(object->GetMaterial()->color,light->GetMaterial()->color);
					}
				}
			}
		}
	}

	return object;
}

inline vector3 VectorProduct( vector3& va, vector3& vb )
{
	return vector3(va.x*vb.x,va.y*vb.y,va.z*vb.z);
}